/*
 * ymodem.c
 *
 *  Created on: Dec 25, 2023
 *      Author: jetpo
 */
#include "ymodem.h"
#include "common.h"

const uint8_t ACK = 0x06;   /* acknowledge */
const uint8_t NAK = 0x15;   /* negative acknowledge */
const uint8_t CRC16 = 0x43; /* 'C' == 0x43, request 16-bit CRC */

#define UART_GETCHAR(rx) HAL_UART_Receive(pUart, (uint8_t *)&rx, 1, PACKET_TIMEOUT) // rx => char
#define UART_SENDCHAR(tx) HAL_UART_Transmit(pUart, (uint8_t *)&tx, 1, 100)          // tx => char

#define YMODEM_SEND_ACK() UART_SENDCHAR(ACK)
#define YMODEM_SEND_C() UART_SENDCHAR(CRC16)
#define YMODEM_SEND_NAK() UART_SENDCHAR(NAK)

uint16_t update_crc16(uint16_t crc_in, uint8_t byte)
{
    uint32_t crc = crc_in;
    uint32_t in = byte | 0x100;

    do
    {
        crc <<= 1;
        in <<= 1;
        if (in & 0x100)
            ++crc;
        if (crc & 0x10000)
            crc ^= 0x1021;
    } while (!(in & 0x10000));

    return crc & 0xffffu;
}

uint16_t cal_crc16(const uint8_t *p_dat, uint32_t size)
{
    uint32_t crc = 0;
    const uint8_t *data_end = p_dat + size;
    while (p_dat < data_end)
        crc = update_crc16(crc, *p_dat++);

    crc = update_crc16(crc, 0);
    crc = update_crc16(crc, 0);

    return crc & 0xffffu;
}

/**
 * @brief 使用ymodem的crc16算法进行校验
 *
 * @param p_dat          待校验的数据, 包含帧头和帧尾
 * @param size              校验的数据长度
 * @return 0: 校验通过;
 *         其它: 校验错误.
 */
int crc_valid(uint8_t *p_dat, uint32_t size)
{
    uint32_t crc;
    crc = p_dat[PACKET_DATA_INDEX + size] << 8;
    crc += p_dat[PACKET_DATA_INDEX + size + 1];

    uint16_t calc_crc = cal_crc16(&p_dat[PACKET_DATA_INDEX], size);
    if (calc_crc != crc)
    {
        return -1;
    }

    return 0;
}

/**
 * @brief 处理1个完整的数据信息包, 不负责校验部分
 *        文件信息包格式
 *        SOH 00 0xFF FILE_NAME 0x00 FILE_LENGHT 0x00 .. 0x00 crc_hight crc_low
 *        其中, 文件长度使用字符串表示
 * @param p_buf             原始数据流
 * @param pout_filename     返回值:解码出的文件名
 * @param pout_filename_len 返回值:解码出的文件名实际长度, 包含最后的0x00
 * @param pout_filelsize    返回值:解码出的文件长度
 * @return  0:解码成功;
 *          其它:解码失败.
 */
int ymodem_parse_fileinfo(uint8_t *p_buf, char *pout_filename, uint16_t *pout_filename_len, uint32_t *pout_filesize)
{
    uint16_t i = 0;
    char *ptr;
    i = 0;
    ptr = (char *)p_buf + PACKET_HEADER_SIZE;
    while ((*ptr) != '\0' && i < FILE_NAME_LENGTH) // 名字的断位符为0
    {
        pout_filename[i] = *(ptr++);
        i += 1;
    }
    pout_filename[i++] = '\0';
    *pout_filename_len = i;

    ptr++;
    uint8_t filesize_str[FILE_SIZE_LENGTH];

    i = 0;
    while ((*ptr) != ' ' && i < FILE_SIZE_LENGTH) // 长度的断位符为空格
    {
        filesize_str[i] = *(ptr++);
        i += 1;
    }
    filesize_str[i++] = '\0';
    str2int(filesize_str, pout_filesize); // 长度的字符串转为数值
    return 0;
}

YmodemReceiveType ymodem_comm(YmodemReceiveSession *p_session,
                              UART_HandleTypeDef *pUart,
                              uint16_t timeout,
                              uint8_t *pout_dat,
                              uint32_t *pout_size,
                              char *pout_filename,
                              uint16_t *pout_filename_len,
                              uint32_t *pout_filesize)
{
    if (YMODEM_WaittingConnection == *p_session)
    {
        HAL_Delay(timeout); /* 延迟等待时间 */
        YMODEM_SEND_C();
    }
    else if (YMODEM_InfoReceived == *p_session)
    {
        // 信息包正常确认
        *p_session = YMODEM_FileReceiving;
        HAL_Delay(50);
        YMODEM_SEND_ACK();
        YMODEM_SEND_C();
    }

    HAL_StatusTypeDef ex;
    uint8_t rx;

    ex = UART_GETCHAR(rx);

    if (HAL_OK != ex)
    {
        return YMODEM_Timeout;
    }

    pout_dat[0] = rx;
    switch (rx)
    {
    case SOH:
        *pout_size = PACKET_DATA_SIZE;
        break;
    case STX:
        *pout_size = PACKET_DATA_1K_SIZE;
        break;
    case EOT:
        *pout_size = 1;
        if (YMODEM_FileReceiving == *p_session)
        {
            *p_session = YMODEM_EOTReceivedOnce;
            YMODEM_SEND_NAK();
            return YMODEM_EOTPacket;
        }
        else if (YMODEM_EOTReceivedOnce == *p_session)
        {
            *p_session = YMODEM_WaittingEndPacket;
            HAL_Delay(50);
            YMODEM_SEND_ACK();
            YMODEM_SEND_C();
            return YMODEM_EOTPacket;
        }
        else /* 两次以上判结束 */
        {
            *p_session = YMODEM_ReceiveSuccess;
            HAL_Delay(50);
            YMODEM_SEND_ACK();
            return YMODEM_EOTPacket;
        }
    case CA:
        *pout_size = 1;
        ex = UART_GETCHAR(rx);
        if (HAL_OK == ex && CA == rx) /* 连续2个CA判发送方主动结束 */
        {
            *p_session = YMODEM_AbortBySender;
            return YMODEM_CAPacket;
        }
    default:
        return YMODEM_ErrorPacket;
    }

    int i;
    // 正常包/结束包, 按长度读获取
    // -----------------------------
    for (i = 1; i < *pout_size + PACKET_OVERHEAD_SIZE; i++)
    {
        ex = UART_GETCHAR(rx);
        if (HAL_OK != ex)
        {
            return YMODEM_ErrorPacket;
        }
        pout_dat[i] = rx;
    }

    // 有效性检查
    // ---------
    if (pout_dat[PACKET_NUMBER_INDEX] + pout_dat[PACKET_CNUMBER_INDEX] != 0xFF) // 序号字校验失败
    {
        HAL_Delay(50);
        YMODEM_SEND_NAK();
        return YMODEM_ErrorPacket;
    }
    if (0 != crc_valid(pout_dat, *pout_size))
    {
        HAL_Delay(50);
        YMODEM_SEND_NAK();
        return YMODEM_ErrorPacket;
    }

    // 正常包/结束包数据处理
    // --------------------
    if (YMODEM_WaittingConnection == *p_session) // 文件信息处理
    {
        ymodem_parse_fileinfo(pout_dat, pout_filename, pout_filename_len, pout_filesize);
        *p_session = YMODEM_InfoReceived;
        return YMODEM_InfoPacket;
    }
    else if (YMODEM_FileReceiving == *p_session) // 文件数据已经读取不须ymodem再处理
    {
        YMODEM_SEND_ACK();
        return YMODEM_FilePacket;
    }
    else // 结束包处理
    {
        *p_session = YMODEM_ReceiveSuccess;
        HAL_Delay(50);
        YMODEM_SEND_ACK();

        HAL_Delay(50);
        YMODEM_SEND_ACK();

        HAL_Delay(50);
        YMODEM_SEND_ACK();
        return YMODEM_EndPacekt;
    }
}
